//////////////////////////////////////////////////
// EASIM project
// easim.net.Field
// 2007/6/6 12:56
// Cong Liu
// gzcong@gmail.com

// Copyright (C) 2007
// All rights reserved.
// Refer to LICENSE of JIST (Java In Simulation Time) for terms and conditions of use.

package easim.net;

import jist.swans.field.Fading;
import jist.swans.field.PathLoss;
import jist.swans.field.Mobility;
import jist.swans.field.Placement;
import jist.swans.field.Spatial;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.radio.RadioNoiseIndep;
import jist.swans.misc.Location;
import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.Constants;

import jist.runtime.JistAPI;

import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;

public class Field implements FieldInterface, JistAPI.DoNotRewrite
{

  public static int NODES = 50;

  protected static jist.swans.field.Field field;

  protected static RadioInfo.RadioInfoShared radioInfo;
  protected static Placement place;
  protected static PathLoss pathLoss;
  protected static Fading fading;
  protected static Spatial spatial;

  protected static HashMap allNodes;

  static {
    init();
    allNodes = new HashMap();
  }

  public static void init() {
    Location.Location2D bounds = new Location.Location2D(FIELD_SIZE, FIELD_SIZE);
    //spatial = new Spatial.HierGrid(bounds, 10);
    //spatial = new Spatial.Grid(bounds, 10);
    spatial = new Spatial.LinearList(bounds);
    pathLoss = new PathLoss.FreeSpace();
    fading = new Fading.None();
    // placement
    place = new Placement.Random(bounds);
    // initialize field
    field = new jist.swans.field.Field(
      spatial, fading, pathLoss, 
      new Mobility.Static(), SENSITIVITY_LIMIT /*Constants.PROPAGATION_LIMIT_DEFAULT*/);
    // initialize shared radio information
    radioInfo = RadioInfo.createShared(
      Constants.FREQUENCY_DEFAULT, // 2.4e9 (Hz)
      Constants.BANDWIDTH_DEFAULT, // 1e6 (1Mb/s)
      Constants.TRANSMIT_DEFAULT, // -1.0 (dBm)
      Constants.GAIN_DEFAULT, // 0.0
      Util.fromDB(SENSITIVITY_LIMIT /*Constants.SENSITIVITY_DEFAULT*/), // -91.0 (dBm)
      Util.fromDB(SENSITIVITY_LIMIT /*Constants.THRESHOLD_DEFAULT*/), // -81.0 (dBm)
      Constants.TEMPERATURE_DEFAULT, // 290.0 (K)
      Constants.TEMPERATURE_FACTOR_DEFAULT, // 10.0
      Constants.AMBIENT_NOISE_DEFAULT); // 0.0
  }

  public static void add(Node node, float x, float y) {
    if (allNodes.containsKey(new Integer(node.id))) {
      throw new RuntimeException("A node with id " + node.id + " already exists.");
    }
    allNodes.put(new Integer(node.id), node);
    Location location = new Location.Location2D(x, y);
    node.radio.setFieldEntity(field.getProxy());
    field.addRadio(node.radio.getRadioInfo(), node.radio.getProxy(), location);
    node.onField = true;
    node.x = x;
    node.y = y;
  }

  public static void move(Node node, float x, float y) {
    Integer radioId = node.radio.getRadioInfo().getUnique().getID();
    Location location = new Location.Location2D(x, y);
    field.moveRadio(radioId, location);
    node.x = x;
    node.y = y;
  }

  public static void delete(Node node) {
    allNodes.remove(new Integer(node.id));
    Integer radioId = node.radio.getRadioInfo().getUnique().getID();
    field.delRadio(radioId);
    node.onField = false;
  }

  //////////////////////////////////////
  // Used by UI
  //

  public static Node[] getAllNodes() {
    return (Node[])allNodes.values().toArray(new Node[0]);
  }

  public static Node[] getNeighbors(float x, float y) {
    SpatialTransmitVisitor transmitVisitor = new SpatialTransmitVisitor();
    Location location = new Location.Location2D(x, y);
    RadioNoiseIndep tempRadio = new RadioNoiseIndep(-1, radioInfo);
    spatial.visitTransmit(transmitVisitor, tempRadio.getRadioInfo(), location, Message.NULL, new Long(1), SENSITIVITY_LIMIT);
    HashSet neighbors = new HashSet();
    Iterator it = transmitVisitor.neighbors.iterator();
    while (it.hasNext()) {
      neighbors.add(allNodes.get(it.next()));
    }
    return (Node[])neighbors.toArray(new Node[0]);
  }

  protected static class SpatialTransmitVisitor implements Spatial.SpatialTransmitVisitor, JistAPI.DoNotRewrite
  {
    public HashSet neighbors = new HashSet();
    public double computeSignal(RadioInfo srcInfo, Location srcLoc, Location dstLoc)
    {
      double loss = pathLoss.compute(srcInfo, srcLoc, srcInfo, dstLoc);
      double fade = fading.compute();
      return srcInfo.getShared().getPower() - loss + fade;
    }
    public void visitTransmit(RadioInfo srcInfo, Location srcLoc, 
        RadioInfo dstInfo, RadioInterface dstEntity, Location dstLoc,
        Message msg, Long durationObj)
    {
      if(srcInfo.getUnique().getID()==dstInfo.getUnique().getID()) return;
      // compute signal strength
      double loss = pathLoss.compute(srcInfo, srcLoc, dstInfo, dstLoc);
      double fade = fading.compute();
      double dstPower = srcInfo.getShared().getPower() - loss + fade;
      // additional cuttoffs
      double dstPower_mW = Util.fromDB(dstPower);
      //if(dstPower_mW < dstInfo.getShared().getBackground_mW()) return;
      if(dstPower_mW < dstInfo.getShared().getSensitivity_mW()) return;

      //if(dstPower_mW < SENSITIVITY_LIMIT) return;
      neighbors.add(dstInfo.getUnique().getID());
    }
  };

}
